查看原文
其他

.NET Core新型 ORM 功能介绍

DotNet 2019-08-02

(给DotNet加星标,提升.Net技能


转自:nicye

cnblogs.com/kellynic/p/10645049.html


简介


FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+。


定义


IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite,
@"Data Source=|DataDirectory|/test.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true) //自动同步实体结构到数据库
.Build();


入门篇


查询


1、查询一条


fsql.Select<Xxx>.Where(a => a.Id == 1).First();


2、分页:第1页,每页20条


fsql.Select<Xxx>.Page(1, 20).ToList();


细节说明:SqlServer 2012 以前的版本,使用 row_number 分页;SqlServer 2012+ 版本,使用最新的 fetch next rows 分页;


3、IN


fsql.Select<Xxx>.Where(a => new { 1,2,3 }.Contains(a.Id)).ToList();


4、联表


fsql.Select<Xxx>.LeftJoin<Yyy>((a, b) => a.YyyId == b.Id).ToList();


5、Exists子表


fsql.Select<Xxx>.Where(a => fsql.Select<Yyy>(b => b.Id == a.YyyId).Any()).ToList();


6、GroupBy & Having


fsql.Select<Xxx>.GroupBy(a => new { a.CategoryId }).Having(a => a.Count > 2).ToList(a => new { a.Key, a.Count() });


7、指定字段查询


fsql.Select<Xxx>.Limit(10).ToList(a => a.Id);
fsql.Select<Xxx>.Limit(10).ToList(a => new { a.Id, a.Name });
fsql.Select<Xxx>.Limit(10).ToList(a => new Dto());


8、执行SQL返回实体


fsql.Ado.Query<Xxx>("select * from xxx");
fsql.Ado.Query<(int, string, string)>("select * from xxx");
fsql.Ado.Query<dynamic>("select * from xxx");


插入


1、单条


fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteAffrows();


2、单条,返回自增值


fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteIdentity();


3、单条,返回插入的行(SqlServer 的 output 特性)


fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteInserted();


4、批量


fsql.Insert<Xxx>().AppendData(数组).ExecuteAffrows();


5、批量,返回插入的行(SqlServer 的 output 特性)


fsql.Insert<Xxx>().AppendData(数组).ExecuteInserted();


6、指定列


fsql.Insert<Xxx>().AppendData(new Xxx()).InsertColumns(a => a.Title).ExecuteAffrows();
fsql.Insert<Xxx>().AppendData(new Xxx()).InsertColumns(a => new { a.Id, a.Title}).ExecuteAffrows();


7、忽略列


fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => a.Title).ExecuteAffrows();
fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => new { a.Id, a.Title}).ExecuteAffrows();


8、事务


fsql.Insert<Xxx>().AppendData(new Xxx()).WithTransaction(事务对象).ExecuteAffrows();


更新


1、指定列


fsql.Update<Xxx>(1).Set(a => a.CreateTime, DateTime.Now).ExecuteAffrows();


2、累加,set clicks = clicks + 1


fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).ExecuteAffrows();


3、保存


fsql.Update<Xxx>().SetSource(单个实体).ExecuteAffrows();


4、批量保存


fsql.Update<Xxx>().SetSource(数组).ExecuteAffrows();


5、忽略列


fsql.Update<Xxx>().SetSource(数组).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ExecuteAffrows();


6、更新条件


fsql.Update<Xxx>().SetSource(数组).Where(a => a.Clicks > 100).ExecuteAffrows();


7、事务


fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).WithTransaction(事务对象).ExecuteAffrows();


删除


1、dywhere


  • 主键值


  • new[] { 主键值1, 主键值2 }


  • Xxx对象


  • new[] { Xxx对象1, Xxx对象2 }


  • new { id = 1 }


fsql.Delete<Xxx>(new[] { 1, 2 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1 OR `Id` = 2)
fsql.Delete<Xxx>(new Xxx { Id = 1, Title = "test" }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
fsql.Delete<Xxx>(new[] { new Xxx { Id = 1, Title = "test" }, new Xxx { Id = 2, Title = "test" } }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1 OR `Id` = 2)
fsql.Delete<Xxx>(new { id = 1 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)


2、条件


fsql.Delete<Xxx>().Where(a => a.Id == 1).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
fsql.Delete<Xxx>().Where("id = ?id", new { id = 1 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (id = ?id)
var item = new Xxx { Id = 1, Title = "newtitle" };
var t7 = fsql.Delete<Xxx>().Where(item).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
var items = new List<Xxx>();
for (var a = 0; a < 10; a++) items.Add(new Xxx { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 });
fsql.Delete<Xxx>().Where(items).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))


3、事务


fsql.Delete<Xxx>().Where(a => a.Id == 1).WithTransaction(事务对象).ExecuteAffrows();


初级篇


表达式


支持功能丰富的表达式函数解析,方便程序员在不了解数据库函数的情况下编写代码。这是 FreeSql 非常特色的功能之一,深入细化函数解析尽量做到满意,所支持的类型基本都可以使用对应的表达式函数,例如 日期、字符串、IN查询、数组(PostgreSQL的数组)、字典(PostgreSQL HStore)等等。


1、查找今天创建的数据


fsql.Delete<Xxx>().Where(a => a.CreateTime.Date == DateTime.Now.Date).ToList();


2、SqlServer 下随机获取记录


fsql.Delete<Xxx>().OrderBy(a => Guid.NewGuid()).Limit(1).ToSql();


3、表达式函数全览



4、数组



一个细节证明 FreeSql 匠心制作


通用的 in 查询 select.Where(a => new []{ 1,2,3 }.Contains(a.xxx))


假设 xxxs 是 pgsql 的数组字段类型,其实会与上面的 in 查询起冲突,FreeSql 解决了这个矛盾 select.Where(a => a.xxxs.Contains(1)) 


5、字典 Dictionary<string, string>



6、JSON JToken/JObject/JArray



7、字符串



使用字符串函数可能会出现性能瓶颈,虽然不推荐使用,但是作为功能库这也是不可缺少的功能之一。


8、日期



9、时间



10、数学函数



11、类型转换



CodeFirst



1、配置实体(特性)


public class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
[Column(IsVersion = true)]
public long versionRow { get; set; }
}


2、在外部配置实体


fsql.CodeFirst
.ConfigEntity<Song>(a => {
a.Property(b => b.Id).IsIdentity(true);
a.Property(b => b.versionRow).IsVersion(true);
});


DbFirst


1、获取所有数据库


fsql.DbFirst.GetDatabases();
//返回字符串数组, ["cccddd", "test"]


2、获取指定数据库的表信息


fsql.DbFirst.GetTablesByDatabase(fsql.DbFirst.GetDatabases()[0]);
//返回包括表、列详情、主键、唯一键、索引、外键、备注等信息


3、生成实体


new FreeSql.Generator.TemplateGenerator()
.Build(fsql.DbFirst,
@"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity",
//模板目录(事先下载)
@"C:\Users\28810\Desktop\你的目录",
//生成后保存的目录
"cccddd"
//数据库
);


高级篇


Repository 仓储实现


1、单个仓储


var curd = fsql.GetRepository<Xxx, int>();
//curd.Find(1);
var item = curd.Get(1);
curd.Update(item);
curd.Insert(item);
curd.Delete(1);
curd.Select.Limit(10).ToList();


工作单元


using (var uow = fsql.CreateUnitOfWork()) {
var songRepos = uow.GetRepository<Song>();
var userRepos = uow.GetRepository<User>();
//上面两个仓储,由同一UnitOfWork uow 创建
//在此执行仓储操作
//这里不受异步方便影响
uow.Commit();
}


局部过滤器 + 数据验证


var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);


之后在使用 topicRepository 操作方法时:


  • 查询/修改/删除时附过滤条件,从而达到不会修改其他用户的数据;


  • 添加时,使用过滤条件验证合法性,若不合法则抛出异常;如以下方法就会报错:


topicRepository.Insert(new Topic { UserId = 2 })


乐观锁


更新实体数据,在并发情况下极容易造成旧数据将新的记录更新。FreeSql 核心部分已经支持乐观锁。


乐观锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。


每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。


无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都会增加 1


DbContext


dotnet add package FreeSql.DbContext


实现类似 EFCore 使用方法,跟踪对象状态,最终通过 SaveChanges 方法以事务的方式提交整段操作。


using (var ctx = new SongContext()) {
var song = new Song { BigNumber = "1000000000000000000" };
ctx.Songs.Add(song);
song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString();
ctx.Songs.Update(song);
var tag = new Tag {
Name = "testaddsublist",
Tags = new[] {
new Tag { Name = "sub1" },
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] {
new Tag { Name = "sub3_01" }
}
}
}
};
ctx.Tags.Add(tag);
ctx.SaveChanges();
}
public class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string BigNumber { get; set; }
[Column(IsVersion = true)] //乐观锁
public long versionRow { get; set; }
}

public class Tag {
[Column(IsIdentity = true)]
public int Id { get; set; }
public int? Parent_id { get; set; }
public virtual Tag Parent { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}

public class SongContext : DbContext {
public DbSet<Song> Songs { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder) {
builder.UseFreeSql(fsql);
}
}


导航属性


支持 1对1、1对多、多对1、多对多 的约定导航属性配置,主要用于表达式内部查询;


//OneToOne、ManyToOne
var t0 = fsql.Select<Tag>().Where(a => a.Parent.Parent.Name == "粤语").ToList();
//OneToMany
var t1 = fsql.Select<Tag>().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToList();
//ManyToMany
var t2 = fsql.Select<Song>().Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语")).ToList();


不朽篇


读写分离


数据库读写分离,本功能是客户端的读写分离行为,数据库服务器该怎么配置仍然那样配置,不受本功能影响,为了方便描术后面讲到的【读写分离】都是指客户端的功能支持。


各种数据库的读写方案不一,数据库端开启读写分离功能后,读写分离的实现大致分为以下几种:


1、nginx代理,配置繁琐且容易出错;


2、中件间,如MyCat,MySql可以其他数据库怎么办?


3、在client端支持;


FreeSql 实现了第3种方案,支持一个【主库】多个【从库】,【从库】的查询策略为随机方式。


若某【从库】发生故障,将切换到其他可用【从库】,若已全部不可用则使用【主库】查询。


出现故障【从库】被隔离起来间隔性的检查可用状态,以待恢复。


IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, connstr)
.UseSlave("connectionString1", "connectionString2")
//使用从数据库,支持多个
.Build();
select.Where(a => a.Id == 1).ToOne();
//读【从库】(默认)
select.Master().WhereId(a => a.Id == 1).ToOne();
//强制读【主库】


下面是以前某项目的测试图片,以供参考,整个过程无感切换和恢复:




分区分表


FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。


var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}");


上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。


合并两个仓储,实现分表下的联表查询:


fsql.GetGuidRepository<User>().Select.FromRepository(logRepository)
.LeftJoin<Log>(b => b.UserId == a.Id)
.ToList();


租户


1、按租户字段区分


FreeSql.Repository 现实了 filter(过滤与验证)功能,如:


var topicRepos = fsql.GetGuidRepository<Topic>(t => t.TerantId == 1);


使用 topicRepos 对象进行 CURD 方法:


  • 在查询/修改/删除时附加此条件,从而达到不会修改 TerantId != 1 的数据;


  • 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;


利用这个功能,我们可以很方便的实现数据分区,达到租户的目的。


2、按租户分表


FreeSql.Repository 现实了 分表功能,如:


var tenantId = 1;
var reposTopic = orm.GetGuidRepository<Topic>(null, oldname => $"{oldname}{tenantId}");


上面我们得到一个仓储按租户分表,使用它 CURD 最终会操作 Topic_1 表。


3、按租户分库


与方案二相同,只是表存储的位置不同。


4、全局设置


通过注入的方式设置仓储类的全局过滤器。


public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeRepository(filter => {
var tenantId = 求出当前租户id;
filter
.Apply<ISoftDelete>("softdelete", a => a.IsDeleted == false)
.Apply<ITenant>("tenant", a => a.TenantId == tenantId)
}, this.GetType().Assembly
);
}


结束语


这次全方位介绍 FreeSql 的功能,只抽取了重要内容发布,由于功能实在太多不方便在一篇文章介绍祥尽。


我个人是非常想展开编写,将每个功能的设计和实现放大来介绍,但还是先希望得到更多人的关注,不然就是一台独角戏了。


推荐阅读

(点击标题可跳转阅读)

Visual Studio 2019极速编码 智能工作 创造未来

.NET Core 3.0创建一个Windows服务

ASP.NET Core 中间件(Middleware)


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

喜欢就点一下「好看」呗~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存